#ifndef RAY_TRACE_PRO_GEOMETRY_H
#define RAY_TRACE_PRO_GEOMETRY_H

#include "BBox.h"
#include "Ray.h"
#include "RayTraceSystem.h"

using namespace GxLibMath;

namespace RayTracePro
{
	class Material;
	class Geometry;
	class DifferentialGeometry;



	bool RayBBoxIntersection(const BBox & bbox, const Ray& ray, float & tmin, float & tmax);
	inline bool RayBBoxIntersection(const BBox & bbox, const Ray& ray, float & tmin, float & tmax, int* isNeg)
	{
		float tymin, tymax, tzmin, tzmax;
		if (isNeg[0]) 
		{
			tmin = (bbox.Max.x - ray.Origin.x) * ray.ReciprocalDirection.x;
			tmax = (bbox.Min.x - ray.Origin.x) * ray.ReciprocalDirection.x;
		}
		else
		{
			tmin = (bbox.Min.x - ray.Origin.x) * ray.ReciprocalDirection.x;
			tmax = (bbox.Max.x - ray.Origin.x) * ray.ReciprocalDirection.x;
		}
		if (isNeg[1]) 
		{
			tymin = (bbox.Max.y - ray.Origin.y) * ray.ReciprocalDirection.y;
			tymax = (bbox.Min.y - ray.Origin.y) * ray.ReciprocalDirection.y;
		}
		else
		{
			tymin = (bbox.Min.y - ray.Origin.y) * ray.ReciprocalDirection.y;
			tymax = (bbox.Max.y - ray.Origin.y) * ray.ReciprocalDirection.y;
		}
		if ( tmin - tymax > Epsilon || tymin - tmax > Epsilon)
			return false;
		if (tymin > tmin)
			tmin = tymin;
		if (tymax < tmax)
			tmax = tymax;
		if (isNeg[2]) 
		{
			tzmin = (bbox.Max.z - ray.Origin.z) * ray.ReciprocalDirection.z;
			tzmax = (bbox.Min.z - ray.Origin.z) * ray.ReciprocalDirection.z;
		}
		else
		{
			tzmin = (bbox.Min.z - ray.Origin.z) * ray.ReciprocalDirection.z;
			tzmax = (bbox.Max.z - ray.Origin.z) * ray.ReciprocalDirection.z;
		}
		if ( tmin - tzmax > Epsilon || tzmin - tmax > Epsilon )
			return false;
		if (tzmin > tmin)
			tmin = tzmin;
		if (tzmax < tmax)
			tmax = tzmax;
		tmin = GxLibMath::Max(ray.tMin, tmin);
		tmax = GxLibMath::Min(ray.tMax, tmax);
		return tmin <= tmax;
	}

	typedef void (*Compute_dNdxFunc)(DifferentialGeometry & inter, const Vec3 & dPdx, const Vec3 & dPdy);
	typedef bool (*ComputeIntersectionFunc)(RayTraceSystem * system, DifferentialGeometry & inter, const Geometry * geom, const Ray & ray, const RayDifferential & rayDiffIn, float & t);
	typedef bool (*ComputeIntersectionPredFunc)(RayTraceSystem * system, const Geometry * geom, const Ray & ray);
	typedef bool (*ComputeBBoxFunc)(BBox & bbox, const Geometry * geom);

	class DifferentialGeometry
	{
	public:
		Vec3 Position;
		Vec3 Normal;
		Vec2 UV;
		int Flags;
		Geometry * Geometry;
		Material * Material;
		float dUdx, dUdy, dVdx, dVdy;
		void * dNdxContinuation;
		Vec3 dNdx, dNdy; // Lazy computation
		static const int DG_HAS_DNDX = 1;

		inline void Init()
		{
			Flags = 0;
		}

		inline int HasDnDx()
		{
			return Flags & DG_HAS_DNDX;
		}

		inline void SetDnDx()
		{
			Flags = (Flags | DG_HAS_DNDX);
		}
	};

	class Geometry : public GxLibBasic::Object
	{
	protected:
		Compute_dNdxFunc Compute_dNdx;
	public:
		Matrix4 Transformation, InverseTransformation;
		Material * Material;
		bool AutoFlipNormals;
		bool CastShadow;
		ComputeIntersectionFunc ComputeIntersection;
		ComputeIntersectionPredFunc ComputeIntersectionPred;
		Geometry()
		{
			Material = 0;
			AutoFlipNormals = false;
			CastShadow = true;
			Matrix4::CreateIdentityMatrix(Transformation);
			Matrix4::CreateIdentityMatrix(InverseTransformation);
		}
		virtual void ComputeBBox(BBox & bbox) = 0;
		virtual void ComputeBoundingSpheres(int depth, GxLibBasic::Array<Vec4, true> & buffer)
		{
			Vec4 sphere;
			BBox box;
			ComputeBBox(box);
			Vec3 position;
			Vec3::Add(position, box.Max, box.Min);
			Vec3::Scale(position, position, 0.5f);
			sphere.x = position.x; sphere.y = position.y; sphere.z = position.z;
			Vec3 range;
			Vec3::Subtract(range, box.Max, box.Min);
			sphere.w = range.Length() * 0.5f;
			buffer.Add(sphere);
		}
		inline void SetTransformation(const Matrix4 & trans)
		{
			Transformation = trans;
			trans.Inverse(InverseTransformation);
		}
		inline static void ComputeDndx(DifferentialGeometry & inter, const Vec3 & dPdx, const Vec3 & dPdy)
		{
			Vec3 t_dPdx, t_dPdy;
			inter.Geometry->InverseTransformation.TransformNormal(t_dPdx, dPdx);
			inter.Geometry->InverseTransformation.TransformNormal(t_dPdy, dPdy);
			inter.Geometry->Compute_dNdx(inter, t_dPdx, t_dPdy);
			Vec3 tmp = inter.dNdx;
			inter.Geometry->Transformation.TransformNormal(inter.dNdx, tmp);
			tmp = inter.dNdy;
			inter.Geometry->Transformation.TransformNormal(inter.dNdy, tmp);
		}
	};

	class SphereGeometry : public Geometry
	{
	private:
		float Radius, Rad2;
		static bool SphereGeometry_ComputeIntersectionPredFunc(RayTraceSystem * system, const Geometry * geom, const Ray & ray);
		static bool SphereGeometry_ComputeIntersectionFunc(RayTraceSystem * system, DifferentialGeometry & inter, const Geometry * geom, const Ray & ray, const RayDifferential & rayDiffIn, float & t);
		static void SphereGeometry_Compute_dNdxFunc(DifferentialGeometry & inter, const Vec3 & dPdx, const Vec3 & dPdy);
	public:
		SphereGeometry(float rad);
		inline void SetRadius(float rad)
		{
			Radius = rad;
			Rad2 = rad * rad;
		}
		inline float GetRadius()
		{
			return Radius;
		}
		virtual void ComputeBBox(BBox & bbox);
		virtual void ComputeBoundingSpheres(int depth, GxLibBasic::Array<Vec4, true> & buffer);
	};
}

#endif